﻿using System;
using System.Runtime.InteropServices;
using AsterNET.FastAGI;
using AsterNET.FastAGI.Command;

namespace AsterNET.Extensions.FastAGI.Helpers
{
    public enum DialStatus
    {
        /// <summary>
        ///     Call is answered. A successful dial. The caller reached the callee.
        /// </summary>
        Answer,

        /// <summary>
        ///     Busy signal. The dial command reached its number but the number is busy.
        /// </summary>
        Busy,

        /// <summary>
        ///     No answer. The dial command reached its number, the number rang for too long, then the dial timed out.
        /// </summary>
        NoAnswer,

        /// <summary>
        ///     Call is cancelled. The dial command reached its number but the caller hung up before the callee picked up.
        /// </summary>
        Cancel,

        /// <summary>
        ///     Congestion. This status is usually a sign that the dialled number is not recognised.
        /// </summary>
        Congestion,

        /// <summary>
        ///     Channel unavailable. On SIP, peer may not be registered.
        /// </summary>
        ChanUnavail,

        /// <summary>
        ///     Privacy mode, callee rejected the call
        /// </summary>
        DontCall,

        /// <summary>
        ///     Privacy mode, callee chose to send caller to torture menu
        /// </summary>
        Torture,

        /// <summary>
        ///     Error parsing Dial command arguments
        /// </summary>
        InvalidARGs
    }

    public class DialOptions
    {
        /// <summary>
        ///     Play an announcement (x.gsm) to the called party.
        /// </summary>
        /// <param name="sound_file"></param>
        /// <returns></returns>
        public static string AnnounceToCallee(string sound_file)
        {
            return string.Format("A({0})", sound_file);
        }

        /// <summary>
        ///     Reset the CDR (Call Detail Record) for this call. This is like using the NoCDR command
        /// </summary>
        public static string ResetCDR()
        {
            return "C";
        }

        /// <summary>
        ///     Sets the channel driver flag that the "call is answered elsewhere" if Dial() cancels the call
        /// </summary>
        public static string AnsweredElsewhere()
        {
            return "c";
        }

        /// <summary>
        ///     After the called party answers, send digits as a DTMF stream, then connect the call to the originating channel (you
        ///     can also use 'w' to produce .5 second pauses). You can also provide digits after a colon - all digits before the
        ///     colon are sent to the called channel, all digits after the colon are sent to the calling channel (all digits are
        ///     sent to the called channel if there is no colon present).
        /// </summary>
        /// <param name="digits"></param>
        /// <returns></returns>
        public static string DTMFAfterAnswer(string digits)
        {
            return string.Format("D({0})", digits);
        }

        /// <summary>
        ///     This flag trumps the 'H' flag and intercepts any single DTMF tone while waiting for the call to be answered and
        ///     jumps to that extension if it exists. This allows you to dial a 1-digit exit extension while waiting for the call
        ///     to be answered - see also RetryDial. This uses the current context unless ${EXITCONTEXT} is defined.
        /// </summary>
        public static string DTMFJump()
        {
            return "d";
        }

        /// <summary>
        ///     Execute "h" as the peer when this call ends
        /// </summary>
        public static string ExecuteHAsPeer()
        {
            return "e";
        }

        /// <summary>
        ///     When the caller hangs up, transfer the called party to the specified context and extension and continue execution.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="extension"></param>
        /// <param name="priority"></param>
        /// <returns></returns>
        public static string TransferOnCallerHangup(string context, string extension, string priority)
        {
            return string.Format("F({0}^{1}^{2})", context, extension, priority);
        }

        /// <summary>
        ///     forces callerid to be set based on a dialplan "hint" for the current channel. For example, some PSTNs don't allow
        ///     callerids from other extensions than the ones that are assigned to you.
        /// </summary>
        public static string ForceCallerIDAsHint()
        {
            return "f";
        }

        /// <summary>
        ///     If the call is answered, transfer both parties to the specified context and extension. The calling party is
        ///     transferred to priority x, and the called party to priority x+1. This allows the dialplan to distinguish between
        ///     the calling and called legs of the call (new in v1.2). You cannot use any options that would affect the post-answer
        ///     state if this option is used.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="extension"></param>
        /// <param name="priority"></param>
        /// <returns></returns>
        public static string TransferPartiesOnAnswer(string context, string extension, string priority)
        {
            return string.Format("G({0}^{1}^{2})", context, extension, priority);
        }

        /// <summary>
        ///     When the called party hangs up, continue to execute commands in the current context at the next priority.
        /// </summary>
        public static string ContinueOnCalleeHangup()
        {
            return "g";
        }

        /// <summary>
        ///     Allow the caller to hang up by dialing * ( * is defined in features.conf -> featuremap -> disconnect )
        /// </summary>
        public static string AllowCallerHangupOnStar()
        {
            return "H";
        }

        /// <summary>
        ///     Allow the callee to hang up by dialing * ( * is defined in features.conf -> featuremap -> disconnect )
        /// </summary>
        public static string AllowCalleeHangupOnStar()
        {
            return "h";
        }

        /// <summary>
        ///     Asterisk will ignore any forwarding requests it may receive on this dial attempt. (new in 1.4) Useful if you are
        ///     ringing a group of people and one person has set their phone to forwarded direct to voicemail on their cell or
        ///     something which normally prevents any of the other phones from ringing.
        /// </summary>
        public static string IgnoreForwardingRequests()
        {
            return "i";
        }

        /// <summary>
        ///     Asterisk 1.2 and later (1.6???): Jump to priority n+101 if all of the requested channels were busy (just like
        ///     behavior in Asterisk 1.0.x)
        /// </summary>
        public static string JumpToOnBusy()
        {
            return "j";
        }

        /// <summary>
        ///     Allow the calling party to enable parking of the call by sending the DTMF sequence defined for call parking in
        ///     features.conf (Asterisk v1.4.x)
        /// </summary>
        public static string AllowCallerToPark()
        {
            return "K";
        }

        /// <summary>
        ///     Allow the called party to enable parking of the call by sending the DTMF sequence defined for call parking in
        ///     features.conf (Asterisk v1.4.x)
        /// </summary>
        public static string AllowCalleeToPark()
        {
            return "k";
        }

        /// <summary>
        ///     <para>
        ///         Limit the call to 'x' ms, warning when 'y' ms are left, repeated every 'z' ms) Only 'x' is required, 'y' and
        ///         'z' are optional.
        ///     </para>
        ///     <para>
        ///         The following special variables are optional for limit calls:
        ///     </para>
        ///     <list type="bullet">
        ///         <item>
        ///             <description>LIMIT_PLAYAUDIO_CALLER - yes|no (default yes) - Play sounds to the caller.</description>
        ///         </item>
        ///         <item>
        ///             <description>LIMIT_PLAYAUDIO_CALLEE - yes|no - Play sounds to the callee.</description>
        ///         </item>
        ///         <item>
        ///             <description>LIMIT_TIMEOUT_FILE - File to play when time is up.</description>
        ///         </item>
        ///         <item>
        ///             <description>LIMIT_CONNECT_FILE - File to play when call begins.</description>
        ///         </item>
        ///         <item>
        ///             <description>
        ///                 LIMIT_WARNING_FILE - File to play as warning if 'y' is defined. If LIMIT_WARNING_FILE is not
        ///                 defined, then the default behavior is to announce ("You have [XX minutes] YY seconds").
        ///             </description>
        ///         </item>
        ///     </list>
        /// </summary>
        /// <param name="limit"></param>
        /// <param name="warnAt"></param>
        /// <param name="repeatEvery"></param>
        /// <returns></returns>
        /// <remarks>
        /// </remarks>
        public static string LimitAndWarn(int limit, int warnAt, int repeatEvery)
        {
            return string.Format("L({0}:{1}:{2})", limit, warnAt, repeatEvery);
        }

        /// <summary>
        ///     Executes the macro (x) upon connect of the call (i.e. when the called party answers).
        ///     See also U. IMPORTANT - The CDR 'billsecs' field is set to zero if the callee answers the call,
        ///     but hangs up whilst the macro is still running (if the callee answers and the macro finishes, 'billsecs' contains
        ///     the correct value).
        /// </summary>
        /// <param name="macro"></param>
        /// <returns></returns>
        public static string ExecuteMacroOnConnect(string macro)
        {
            return string.Format("M({0})", macro);
        }

        /// <summary>
        ///     Provide Music on Hold to the calling party until the called channel answers.
        ///     This is mutually exclusive with option 'r', obviously. Use m(class) to specify a class for the music on hold.
        /// </summary>
        public static string UseMusicOnHold()
        {
            return "m";
        }

        /// <summary>
        ///     Modifies the privacy manager - turns off call screening if caller ID information is present
        /// </summary>
        public static string TurnOffCallScreening()
        {
            return "N";
        }

        /// <summary>
        ///     (Asterisk 1.6) If delete is 0 or not specified, delete the privacy manager introduction if the caller hangs up
        ///     before the call is answered.
        ///     If set to 1, delete the recording even if the call is answered.
        /// </summary>
        /// <param name="delete"></param>
        /// <returns></returns>
        public static string PrivacyManagerIntroduction(int delete)
        {
            return string.Format("n({0})", delete);
        }

        /// <summary>
        ///     If mode is set to 1 or isn't specified, ringback immediately if the originator hangs up.
        ///     If mode is set to 2, ring back when the operator flashes the trunk. This is only valid when the caller and called
        ///     channels are DAHDI channels. It is intended for calling an operator station.
        /// </summary>
        /// <param name="mode"></param>
        /// <returns></returns>
        public static string Mode(int mode)
        {
            return string.Format("O({0})", mode);
        }

        /// <summary>
        ///     Restore the Asterisk v1.0 CallerId behavior (send the original caller's ID) in Asterisk v1.2 (default: send this
        ///     extension's number)
        /// </summary>
        public static string UseV1CallerID()
        {
            return "o";
        }
    }

    public class DialHelper
    {
        /// <summary>
        /// </summary>
        /// <param name="peer">
        ///     <para>Combination of Type/Identifier</para>
        ///     <para>
        ///         TYPE: specifies the channel type. It should be one of the registered channel types, such as "Zap", "SIP",
        ///         "IAX2", and so on.
        ///     </para>
        ///     <para>
        ///         IDENTIFIER: specifies the "phone number" to dial on that channel. The format of the "phone number" depends on
        ///         the channel, and may contain additonal parameters (e.g. a distinctive ring parameter) specific to the channel
        ///         module in question; the Dial command simply passes identifier to the channel module to process in whatever way
        ///         is appropriate. See the documentation for the individual channel modules to learn about the correct format for
        ///         specifying the identifier for the Dial command, and the options available to you when doing so. If you need a
        ///         .5 second pause while dialing a number you can insert a w in the appropriate place.
        ///     </para>
        /// </param>
        /// <param name="timeout">
        ///     <para>
        ///         The timeout parameter is optional. If not specified, the Dial command will wait indefinitely, exiting only
        ///         when the originating channel hangs up, or all the dialed channels return a busy or error condition. Otherwise
        ///         it specifies a maximum time, in seconds, that the Dial command is to wait for a channel to answer.
        ///     </para>
        /// </param>
        /// <param name="url">
        ///     <para>
        ///         The optional URL parameter will also be sent to the called party upon successful connection, if the channel
        ///         technology supports the sending of URLs in this way.
        ///     </para>
        ///     <para>Use NULL if not applicable</para>
        /// </param>
        /// <param name="options"></param>
        /// <returns>ExecCommand</returns>
        public static ExecCommand Dial(string peer, int timeout, [Optional] string url,
            [Optional] params string[] options)
        {
            string rtn;
            if (options == null)
                rtn = string.Format("{0},{1}",
                    peer, timeout);
            else
                switch (url)
                {
                    case null:
                        rtn = string.Format("{0},{1},{2}",
                            peer, timeout, String.Join(",", options));
                        break;
                    default:
                        rtn = string.Format("{0},{1},{2},{3}",
                            peer, timeout, String.Join(",", options), url);
                        break;
                }

            return new ExecCommand("Dial", rtn);
        }

        /// <summary>
        /// </summary>
        /// <param name="channel">The currently executing AGI Channel</param>
        /// <param name="peer">
        ///     <para>Combination of Type/Identifier</para>
        ///     <para>
        ///         TYPE: specifies the channel type. It should be one of the registered channel types, such as "Zap", "SIP",
        ///         "IAX2", and so on.
        ///     </para>
        ///     <para>
        ///         IDENTIFIER: specifies the "phone number" to dial on that channel. The format of the "phone number" depends on
        ///         the channel, and may contain additonal parameters (e.g. a distinctive ring parameter) specific to the channel
        ///         module in question; the Dial command simply passes identifier to the channel module to process in whatever way
        ///         is appropriate. See the documentation for the individual channel modules to learn about the correct format for
        ///         specifying the identifier for the Dial command, and the options available to you when doing so. If you need a
        ///         .5 second pause while dialing a number you can insert a w in the appropriate place.
        ///     </para>
        /// </param>
        /// <param name="timeout">
        ///     <para>
        ///         The timeout parameter is optional. If not specified, the Dial command will wait indefinitely, exiting only
        ///         when the originating channel hangs up, or all the dialed channels return a busy or error condition. Otherwise
        ///         it specifies a maximum time, in seconds, that the Dial command is to wait for a channel to answer.
        ///     </para>
        /// </param>
        /// <param name="url">
        ///     <para>
        ///         The optional URL parameter will also be sent to the called party upon successful connection, if the channel
        ///         technology supports the sending of URLs in this way.
        ///     </para>
        ///     <para>Use NULL if not applicable</para>
        /// </param>
        /// <param name="options"></param>
        /// <returns>DialStatus</returns>
        public static DialStatus Dial(
            AGIChannel channel,
            string peer,
            int timeout,
            [Optional] string url,
            [Optional] params string[] options)
        {
            string rtn;
            if (options == null)
                rtn = string.Format("{0},{1}",
                    peer, timeout);
            else
                switch (url)
                {
                    case null:
                        rtn = string.Format("{0},{1},{2}",
                            peer, timeout, String.Join(",", options));
                        break;
                    default:
                        rtn = string.Format("{0},{1},{2},{3}",
                            peer, timeout, String.Join(",", options), url);
                        break;
                }

            channel.SendCommand(new ExecCommand("Dial", rtn));
            channel.SendCommand(new GetVariableCommand("DIALSTATUS"));
            return (DialStatus) Enum.Parse(typeof (DialStatus), channel.LastReply.Extra, true);
        }
    }
}